﻿using DataConvertLib;
using PLC.Interface.Devices;
using PLC.Interface.Groups;
using PLC.Interface.RecipeVariables;
using PLC.Interface.Variables;
using PLCWpfTest.Models;
using Serilog;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using YWM;
using YWM.Common.Extensions;
using YWM.Communication;
using YWM.Exceptions;
using YWM.Localization;

namespace PLCWpfTest.Common
{
    public sealed class CommunicationCache
    {
        /// <summary>
        /// 日志添加事件字段
        /// </summary>
        private static event Action<SysLogModel> _logAdd;

        /// <summary>
        /// 取消令牌源
        /// </summary>
        private static CancellationTokenSource _cts;

        /// <summary>
        /// 重连次数
        /// </summary>
        private static int _connectCount;

        /// <summary>
        /// 是否重连
        /// </summary>
        private static bool _isReconnect;

        /// <summary>
        /// 重连延时
        /// </summary>
        private static int _reconnectDelay = 2000;

        /// <summary>
        /// 通信工具
        /// </summary>
        private static CommunicationUtil _communicationUtil = new TCPUtil();

        /// <summary>
        /// ModBus功能
        /// </summary>
        private static ModBusFunction[] _modBusFunctions = (ModBusFunction[])Enum.GetValues(typeof(ModBusFunction));

        /// <summary>
        /// 通信设备是否连接
        /// </summary>
        public static bool Connected
        {
            get
            {
                return _communicationUtil.Connected;
            }
        }

        /// <summary>
        /// 系统日志集合
        /// </summary>
        public static ObservableCollection<SysLogModel> SysLogs { get; set; } = new ObservableCollection<SysLogModel>();

        /// <summary>
        /// 通信配置
        /// </summary>
        public static DeviceDto Device { get; set; }

        /// <summary>
        /// 通信组配置集合
        /// </summary>
        public static IReadOnlyList<GroupResultDto> Groups { get; set; }

        /// <summary>
        /// 以变量Id为Key,变量为Value的字典
        /// </summary>
        public static Dictionary<long, VariableResultDto> LongVariablePairs { get; set; } = new Dictionary<long, VariableResultDto>();

        /// <summary>
        /// 变量报警触发消除事件字段
        /// </summary>
        public static event Action<bool, VariableDto> VariableAlarm;

        /// <summary>
        /// 初始化
        /// </summary>
        public static void Initialize()
        {
            _logAdd += CommunicationCache_LogAdd;
            ILocalizationManager lang = YApp.Resolve<ILocalizationManager>();
            if (Device == null)
            {
                AddLog(1, lang["请完善通信配置"]);
                return;
            }
            if (Groups?.Count == 0)
            {
                AddLog(1, lang["请完善通信组配置"]);
                return;
            }
            _cts = new CancellationTokenSource();
            Task.Run(async () => await Communication(), _cts.Token);
        }

        /// <summary>
        /// 通信
        /// </summary>
        private static async Task Communication()
        {
            _connectCount = 0;
            ILocalizationManager lang = YApp.Resolve<ILocalizationManager>();
            while (!_cts.IsCancellationRequested)
            {
                if (!_communicationUtil.Connected)
                {
                    await TryConnect(lang);
                    continue;
                }

                foreach (GroupResultDto group in Groups)
                {
                    if (group.StoreArea < 2)
                    {
                        continue;
                    }
                    (byte[] sendedBuffer, int count) = ModBusTCPUtil.GetReadRequestMessage(1, _modBusFunctions[(int)group.StoreArea], (short)group.StartAddress, (short)group.Length);
                    (byte[] receivedBuffer, Exception ex) = await _communicationUtil.SendAndReceiveAsync(sendedBuffer);
                    if (ex != null)
                    {
                        Log.Logger.Error(ex, ex.Message);
                        break;
                    }
                    ModBusAnalysis modBusAnalysis = ModBusTCPUtil.AnalysisResponseMessage(sendedBuffer, receivedBuffer, count);
                    foreach (VariableResultDto variable in group.Variables)
                    {
                        int startIndex = (variable.StartIndex - group.StartAddress) * 2;
                        variable.Value = DataConvertorUtil.GetOneValueFromBytes(modBusAnalysis.Data, (DataType)variable.DataType, startIndex, variable.OffsetOrLength);
                        variable.Value = MigrationLib.GetMigrationValue(variable.Value, variable.Scale.ToString(), variable.Offset.ToString()).Content;
                        if (LongVariablePairs.ContainsKey(variable.Id))
                        {
                            LongVariablePairs[variable.Id] = variable;
                        }
                        else
                        {
                            LongVariablePairs.Add(variable.Id, variable);
                        }
                        CheckAlarm(variable);
                    }
                }
            }
        }

        /// <summary>
        /// 尝试连接
        /// </summary>
        /// <param name="lang"></param>
        /// <returns></returns>
        private static async Task TryConnect(ILocalizationManager lang)
        {
            //断线重连，三次之后结束循环并添加日志
            if (_connectCount >= 3)
            {
                AddLog(1, lang["请检查通信配置"]);
                _cts.Cancel();
            }
            else
            {
                if (_isReconnect)
                {
                    _communicationUtil.Disconnect();
                    await Task.Delay(_reconnectDelay);
                }
                (bool rs, Exception ex) = _communicationUtil.Connect(Device.IP, Device.Port);
                if (!rs)
                {
                    AddLog(1, lang[_isReconnect ? "通信设备重连失败" : "通信设备连接失败"]);
                    Log.Logger.Error(ex, ex.Message);
                    _connectCount++;
                }
                else
                {
                    AddLog(0, lang[_isReconnect ? "通信设备重连成功" : "通信设备连接成功"]);
                    _connectCount = 0;
                }
                if (!_isReconnect)
                {
                    _isReconnect = true;
                }
            }
        }

        /// <summary>
        /// 检测报警
        /// </summary>
        /// <param name="variable"></param>
        private static void CheckAlarm(VariableResultDto variable)
        {
            if (variable.PosAlarm)
            {
                bool currentValue = variable.Value.ToString() == "True";
                if (!variable.PosCacheValue && currentValue)
                {
                    //检测到了上升沿报警触发
                    VariableAlarm?.Invoke(true, variable);
                }
                else if (variable.PosCacheValue && !currentValue)
                {
                    //检测到了上升沿报警消除
                    VariableAlarm?.Invoke(false, variable);
                }
                variable.PosCacheValue = currentValue;
            }

            if (variable.NegAlarm)
            {
                bool currentValue = variable.Value.ToString() == "True";
                if (variable.NegCacheValue && !currentValue)
                {
                    //检测到了下降沿报警触发
                    VariableAlarm?.Invoke(true, variable);
                }
                else if (!variable.NegCacheValue && currentValue)
                {
                    //检测到了下降沿报警消除
                    VariableAlarm?.Invoke(false, variable);
                }
                variable.NegCacheValue = currentValue;
            }
        }

        /// <summary>
        /// 应用配方
        /// </summary>
        /// <param name="recipeVariables"></param>
        /// <returns></returns>
        public static async Task ApplyRecipe(IReadOnlyList<RecipeVariableDto> recipeVariables)
        {
            foreach (GroupResultDto group in Groups)
            {
                if (group.StoreArea != 2)
                {
                    continue;
                }
                foreach (VariableResultDto variable in group.Variables)
                {
                    string set = recipeVariables.FirstOrDefault(r => r.VariableId == variable.Id)?.Value;
                    if (set.IsNullOrWhiteSpace() || set.Equals(variable.Value.ToString()))
                    {
                        continue;
                    }
                    await UpdateVariableValue(group, variable, set, "应用配方失败");
                }
            }
        }

        /// <summary>
        /// 修改参数
        /// </summary>
        /// <param name="id"></param>
        /// <param name="set"></param>
        /// <returns></returns>
        public static async Task UpdateParameter(long id, string set)
        {
            foreach (GroupResultDto group in Groups)
            {
                if (group.StoreArea != 2)
                {
                    continue;
                }
                VariableResultDto variable = group.Variables.FirstOrDefault(v => v.Id == id);
                if (variable == null)
                {
                    continue;
                }
                await UpdateVariableValue(group, variable, set, "参数修改失败");
            }
        }

        /// <summary>
        /// 更新变量值
        /// </summary>
        /// <param name="group"></param>
        /// <param name="variable"></param>
        /// <param name="set"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        private static async Task UpdateVariableValue(GroupResultDto group, VariableResultDto variable, string set, string message)
        {
            DataType dataType = (DataType)variable.DataType;
            if (dataType == DataType.Bool || dataType == DataType.Byte)
            {
                return;
            }
            short startIndex = (short)(variable.StartIndex - group.StartAddress);
            string value = MigrationLib.SetMigrationValue(set, (DataType)variable.DataType, variable.Scale.ToString(), variable.Offset.ToString()).Content;
            byte[] sendedBuffer;
            int count;
            switch (dataType)
            {
                case DataType.Short:
                case DataType.UShort:
                    (sendedBuffer, count) = ModBusTCPUtil.Get0x06RequestMessage(1, startIndex, Convert.ToInt16(value));
                    break;
                default:
                    byte[] data = DataConvertorUtil.GetBytesFromOneValue(dataType == DataType.ByteArray ? DataType.HexString : dataType, value);
                    (sendedBuffer, count) = ModBusTCPUtil.Get0x10RequestMessage(1, startIndex, data);
                    break;
            }
            (byte[] receivedBuffer, Exception ex) = await _communicationUtil.SendAndReceiveAsync(sendedBuffer);
            if (ex != null)
            {
                Log.Logger.Error(ex, ex.Message);
                throw Oops.Oh(message);
            }
            ModBusAnalysis modBusAnalysis = ModBusTCPUtil.AnalysisResponseMessage(sendedBuffer, receivedBuffer, count);
            if (modBusAnalysis.Data == null)
            {
                throw Oops.Oh(message);
            }
        }

        /// <summary>
        /// 取消初始化
        /// </summary>
        public static void Uninitialize()
        {
            if (_communicationUtil.Connected)
            {
                _cts.Cancel();
                _communicationUtil.Disconnect();
                AddLog(0, YApp.Resolve<ILocalizationManager>()["通信设备连接断开"]);
            }
            _isReconnect = false;
            _logAdd -= CommunicationCache_LogAdd;
        }

        /// <summary>
        /// 添加报警日志
        /// </summary>
        /// <param name="level"></param>
        /// <param name="message"></param>
        public static void AddLog(int level, string message)
        {
            _logAdd?.Invoke(new SysLogModel(level, message));
        }

        /// <summary>
        /// 日志添加事件
        /// </summary>
        /// <param name="sysLog"></param>
        private static void CommunicationCache_LogAdd(SysLogModel sysLog)
        {
            Application.Current.Dispatcher.Invoke(() => SysLogs.Insert(0, sysLog));
        }
    }
}
